home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue47 / Differ / CGIAPI.pas next >
Encoding:
Pascal/Delphi Source File  |  1999-06-07  |  16.3 KB  |  550 lines

  1. {  **************************************************
  2.     Originally written by Steve Troxell for The Delphi
  3.     Magazine. Developing Dynamic Web Pages, Issue 16,
  4.     December 1996
  5.    **************************************************  }
  6. {  **************************************************
  7.     Modifyed by Paul Warren for The Delphi Magazine.
  8.     April 1999.
  9.    **************************************************  }
  10.  
  11. unit CGIAPI;
  12.  
  13. interface
  14.  
  15. uses SysUtils, Windows, Classes, INIFIles;
  16.  
  17. type
  18.   TEnvironmentType = (etStdCGI, etWinCGI);
  19.  
  20.   TCGI = class
  21.   private
  22.     FCGIItems: TStringList;
  23.     FFormItems: TStringList;
  24.     FEnvironmentType: TEnvironmentType;
  25.     FOutputFile: TextFile;
  26.     FWinCGIProfileName: string;
  27.     FWinCGIProfile: TINIFile;
  28.     function SearchBuf(Regex: string; const Buffer; Count: integer): integer;
  29.     function UniqueLabelStr(Value: string): string;
  30.   protected
  31.     procedure LoadStdCGIUserData;
  32.     procedure LoadWinCGIUserData;
  33.     procedure LoadMultiCGIUserData;
  34.     procedure UnpackURLString(S: PChar); virtual;
  35.   public
  36.     constructor Create; virtual;
  37.     destructor Destroy; override;
  38.     procedure DumpWinCGIProfile;
  39.     function GetEnv(Variable: string): string;
  40.     { commented out by Paul Warren}
  41.     { procedure Write(Value: string);}
  42.     { procedure WriteLn(Value: string);}
  43.     property CGIItems: TStringList read FCGIItems;
  44.     property EnvironmentType: TEnvironmentType read FEnvironmentType;
  45.     property FormItems: TStringList read FFormItems;
  46.     property OutputFile: TextFile read FOutputFile write FOutputFile;
  47.     property WinCGIProfile: TINIFile read FWinCGIProfile;
  48.   end;
  49.  
  50. var
  51.   CGI: TCGI;
  52.  
  53. implementation
  54.  
  55. const
  56.   NumCGIVars = 15;
  57.   UpldrDir = 'c:\temp\';
  58.  
  59.   { These are the standard names used by the calling application
  60.     to reference CGI variables.  They generally follow the
  61.     WinCGI names. }
  62.   CGIVarNames: array[0..NumCGIVars - 1] of string[31] =
  63.     ('SERVER SOFTWARE',
  64.      'SERVER NAME',
  65.      'SERVER PORT',
  66.      'CGI VERSION',
  67.      'REQUEST PROTOCOL',
  68.      'REQUEST METHOD',
  69.      'LOGICAL PATH',
  70.      'PHYSICAL PATH',
  71.      'EXECUTABLE PATH',
  72.      'QUERY STRING',
  73.      'REMOTE HOST',
  74.      'REMOTE ADDRESS',
  75.      'REMOTE USER',
  76.      'CONTENT LENGTH',
  77.      'CONTENT TYPE');
  78.  
  79.   { These are the actual variable names used by each protocol. }
  80.   CGIVars: array[0..NumCGIVars - 1, TEnvironmentType] of string[31] =
  81.      { etStdCGI              etWinCGI }
  82.     (
  83.      ('SERVER_SOFTWARE',    'SERVER SOFTWARE'),
  84.      ('SERVER_NAME',        'SERVER NAME'),
  85.      ('SERVER_PORT',        'SERVER PORT'),
  86.      ('GATEWAY_INTERFACE',  'CGI VERSION'),
  87.      ('SERVER_PROTOCOL',    'REQUEST PROTOCOL'),
  88.      ('REQUEST_METHOD',     'REQUEST METHOD'),
  89.      ('PATH_INFO',          'LOGICAL PATH'),
  90.      ('PATH_TRANSLATED',    'PHYSICAL PATH'),
  91.      ('SCRIPT_NAME',        'EXECUTABLE PATH'),
  92.      ('QUERY_STRING',       'QUERY STRING'),
  93.      ('REMOTE_HOST',        'REMOTE HOST'),
  94.      ('REMOTE_ADDR',        'REMOTE ADDRESS'),
  95.      ('REMOTE_USER',        'REMOTE USER'),
  96.      ('CONTENT_LENGTH',     'CONTENT LENGTH'),
  97.      ('CONTENT_TYPE',       'CONTENT TYPE')
  98.      );
  99.  
  100. {function ParseIt(var ParseStr: string; Delimiter: string): string;
  101. var
  102.   Len: integer;
  103. begin
  104.   Result := '';
  105.   if Length(ParseStr) > 0 then // if there is something to parse...
  106.   begin
  107.     if Pos(Delimiter, ParseStr) = 1 then  // if it begins with a delimiter...
  108.       System.Delete(ParseStr, 1, Length(Delimiter)); // then delete it
  109.     if Pos(Delimiter, ParseStr) <> 0 then  // if there is a delimiter...
  110.     begin
  111.       // copy up to it
  112.       Len := Pos(Delimiter, ParseStr);
  113.       Result := System.Copy(ParseStr, 1, Len-1);
  114.     end else
  115.     begin
  116.       // else copy all remaining string
  117.       Len := Length(ParseStr);
  118.       Result := System.Copy(ParseStr, 1, Len);
  119.     end;
  120.     // delete what we copied
  121.     System.Delete(ParseStr, 1, Len+Length(Delimiter));
  122.   end;
  123. end;}
  124.  
  125. constructor TCGI.Create;
  126. var
  127.   I: Integer;
  128. begin
  129.   inherited Create;
  130.  
  131.   FCGIItems := TStringList.Create;
  132.   FFormItems := TStringList.Create;
  133.  
  134.   { Detect whether we are standard CGI or WinCGI. }
  135.   if GetEnv('SERVER_NAME') <> '' then
  136.     FEnvironmentType := etStdCGI
  137.   else
  138.   begin
  139.     FEnvironmentType := etWinCGI;
  140.     FWinCGIProfileName := ParamStr(1);
  141.     FWinCGIProfile := TINIFile.Create(FWinCGIProfileName);
  142.   end;
  143.  
  144.   { Assign and open our output file accordingly. }
  145.   case EnvironmentType of
  146.     etStdCGI: AssignFile(OutputFile, '');
  147.     etWinCGI: AssignFile(OutputFile, WinCGIProfile.ReadString('System', 'Output File', ''));
  148.   end;
  149.   Rewrite(OutputFile);
  150.  
  151.   { commented out by Paul Warren}
  152.   { Write standard HTML header for the output page. }
  153.   { WriteLn('Content-type: text/html');}
  154.   { WriteLn('');}
  155.  
  156.   { Load CGI variables and user's form variables. }
  157.   case EnvironmentType of
  158.     etStdCGI: begin
  159.                 for I := 0 to NumCGIVars - 1 do
  160.                   FCGIItems.Values[CGIVarNames[I]] :=
  161.                     GetEnv(CGIVars[I, etStdCGI]);
  162.  
  163.                 if Pos('multipart/form-data', FCGIItems.Values['CONTENT TYPE']) <> 0 then
  164.                 begin
  165.                   FCGIItems.Values['CONTENT BOUNDARY'] :=
  166.                     Copy(FCGIItems.Values['CONTENT TYPE'],
  167.                       Pos('boundary=', FCGIItems.Values['CONTENT TYPE'])+9,
  168.                         Length(FCGIItems.Values['CONTENT TYPE']));
  169.                   LoadMultiCGIUserData;
  170.                 end else
  171.                   LoadStdCGIUserData;
  172.               end;
  173.     etWinCGI: begin
  174.                 for I := 0 to NumCGIVars - 1 do
  175.                   FCGIItems.Values[CGIVarNames[I]] :=
  176.                     WinCGIProfile.ReadString('CGI', CGIVars[I, etWinCGI], '');
  177.  
  178.                 LoadWinCGIUserData;
  179.               end;
  180.   end;
  181. end;
  182.  
  183. destructor TCGI.Destroy;
  184. begin
  185.   CloseFile(OutputFile);
  186.  
  187.   FCGIItems.Free;
  188.   FFormItems.Free;
  189.   FWinCGIProfile.Free;
  190. end;
  191.  
  192. procedure TCGI.DumpWinCGIProfile;
  193. { Writes the contents of the WinCGI profile file to the
  194.   response page. }
  195. var
  196.   FCB: TextFile;
  197.   Rec: string;
  198. begin
  199.   if FWinCGIProfile <> nil then
  200.   begin
  201.     AssignFile(FCB, FWinCGIProfileName);
  202.     Reset(FCB);
  203.     try
  204.       while not Eof(FCB) do
  205.       begin
  206.         ReadLn(FCB, Rec);
  207.         WriteLn(Rec + '<BR>');
  208.       end;
  209.     finally
  210.       CloseFile(FCB);
  211.     end;
  212.   end;
  213. end;
  214.  
  215. function TCGI.GetEnv(Variable: string): string;
  216. { Returns the value iof the given environment variable. }
  217. var
  218.   EnvVariable: array[0..127] of char;
  219.   EnvBuffer: array[0..1023] of char;
  220. begin
  221.   StrPCopy(EnvVariable, Variable);
  222.   Result := '';
  223.   if GetEnvironmentVariable(PChar(Variable), @EnvBuffer, SizeOf(EnvBuffer)) <> 0 then
  224.     Result := StrPas(EnvBuffer);
  225. end;
  226.  
  227. procedure TCGI.LoadStdCGIUserData;
  228. { Reads, parses, and decodes values for the standard CGI form variables. }
  229. var
  230.   ContentLength: LongInt;
  231.   InputFCB: File;
  232.   InputBuffer: PChar;
  233.   RequestMethod: string;
  234.   UserContentBuffer: string;
  235. begin
  236.   RequestMethod := Uppercase(FCGIItems.Values['REQUEST METHOD']);
  237.  
  238.   { If the form action is a POST, then we get form variables from
  239.     the standard input device. }
  240.   if RequestMethod = 'POST' then
  241.   begin
  242.     if FCGIItems.Values['CONTENT TYPE'] <> '' then
  243.     begin
  244.       ContentLength := StrToInt(FCGIItems.Values['CONTENT LENGTH']);
  245.       AssignFile(InputFCB, '');  { standard input }
  246.       Reset(InputFCB, 1);
  247.       try
  248.         InputBuffer := StrAlloc(ContentLength + 1);
  249.         FillChar(InputBuffer^, ContentLength + 1, #0);
  250.         try
  251.           BlockRead(InputFCB, InputBuffer^, ContentLength);
  252.           UnpackURLString(InputBuffer);
  253.         finally
  254.           StrDispose(InputBuffer);
  255.         end;
  256.       finally
  257.         CloseFile(InputFCB);
  258.       end;
  259.     end;
  260.   end
  261.  
  262.   { If the form action is GET, then we get form variables from
  263.     from the QUERY STRING variable. }
  264.   else if RequestMethod = 'GET' then
  265.   begin
  266.     UserContentBuffer := FCGIItems.Values['QUERY STRING'];
  267.     InputBuffer := StrAlloc(Length(UserContentBuffer));
  268.     try
  269.       StrPCopy(InputBuffer, UserContentBuffer);
  270.       UnpackURLString(InputBuffer);
  271.     finally
  272.       StrDispose(InputBuffer);
  273.     end;
  274.   end;
  275. end;
  276.  
  277. // added by Paul Warren
  278. { SearchBuf - execute search on Buffer, modified Boyer-Moore }
  279. function TCGI.SearchBuf(Regex: string; const Buffer; Count: integer): integer;
  280. var
  281.   i, j: integer;
  282.   M, N: integer;
  283.   Skip: array[Char] of integer;
  284.  
  285.   procedure InitSkip;
  286.   var
  287.     Ch: Char;
  288.     i: Integer;
  289.   begin
  290.     for Ch := Low(Char) to High(Char) do Skip[Ch] := M;
  291.     for i := 1 to M do Skip[Regex[i]] := M - i;
  292.   end;
  293.  
  294.   function BufChar(Index: integer): Char;
  295.   begin
  296.      Result := Chr(TByteArray(Buffer)[Index-1]);
  297.   end;
  298.  
  299. begin
  300.   Result := -1; { return -1 if unsuccessful }
  301.   if (Count = 0) or (Regex = '') then exit;
  302.   M := Length(Regex);
  303.   N := Count + 1;
  304.   i := M; j := M;
  305.   InitSkip;
  306.   repeat
  307.     if (BufChar(i) = Regex[j]) then begin
  308.       Dec(i);
  309.       Dec(j);
  310.     end else begin
  311.       if M - j + 1 > Skip[BufChar(i)] then
  312.         i := i + M - j + 1
  313.       else
  314.         i := i + Skip[BufChar(i)];
  315.       j := M;
  316.     end;
  317.   until (j < 1) or (i > N); { found something or reached end }
  318.   if (i > N) then
  319.     Result := -1  { no match - reached end }
  320.   else
  321.     Result := i;  { match begining at i }
  322. end;
  323.  
  324. procedure TCGI.LoadMultiCGIUserData;
  325. { Reads, parses, and decodes values for the standard CGI
  326.   form variables in a multipart form. }
  327. const
  328.   Eom: boolean = false;
  329.   FileCounter: integer = 0;
  330. var
  331.   ContentLength: LongInt;
  332.   InputFCB: File;
  333.   RequestMethod: string;
  334.   S: string;
  335.   LabelStr: String;
  336.   LastLabelStr: String;
  337.   ValueStr: String;
  338.   Buffer: array of char;
  339.   AttachStream: TMemoryStream;
  340.   UniqueFileName: string;
  341.  
  342.   function read1ln(var Value: string): integer;
  343.   begin
  344.     Result := SearchBuf(#13#10, Buffer[0], ContentLength)+2;
  345.     SetLength(Value, Result);
  346.     Move(Buffer[0], Value[1], Result);
  347.     Move(Buffer[Result], Buffer[0], Length(Buffer)-Result);
  348.   end;
  349.  
  350.   function readAttachment: integer;
  351.   begin
  352.     Result := SearchBuf(#13#10'--'+CGIItems.Values['CONTENT BOUNDARY'], Buffer[0], ContentLength);
  353.     AttachStream.Write(Buffer[0], Result);
  354.     Move(Buffer[Result], Buffer[0], Length(Buffer)-Result);
  355.   end;
  356.  
  357. begin
  358.   RequestMethod := Uppercase(FCGIItems.Values['REQUEST METHOD']);
  359.   if RequestMethod = 'POST' then
  360.   begin
  361.     if FCGIItems.Values['CONTENT TYPE'] <> '' then
  362.     begin
  363.       ContentLength := StrToInt(FCGIItems.Values['CONTENT LENGTH']);
  364.       AssignFile(InputFCB, '');  { standard input }
  365.       Reset(InputFCB, 1);
  366.       try
  367.         SetLength(Buffer, ContentLength);
  368.         BlockRead(InputFCB, Buffer[0], ContentLength);
  369.         while not Eom do
  370.         begin
  371.           read1ln(S); // read a line
  372.  
  373.           if S <> #13#10 then
  374.           begin
  375.             while true do
  376.             begin
  377.               if Pos('Content-Disposition', S) <> 0 then
  378.               begin
  379.                 System.Delete(S, 1, Pos('"', S)); // delete to first "
  380.                 LabelStr := System.Copy(S, 1, Pos('"', S)-1); // copy name
  381.                 System.Delete(S, 1, Pos('"', S)); // delete name
  382.                 if Pos('FILENAME', uppercase(S)) <> 0 then
  383.                 begin
  384.                   LabelStr := UniqueLabelStr('FILENAME');
  385.                   LastLabelStr := LabelStr;
  386.                   System.Delete(S, 1, Pos('"', S)); // delete to filename
  387.                   ValueStr := System.Copy(S, 1, Pos('"', S)-1); // copy value
  388.  
  389.                   if ValueStr <> '' then
  390.                   begin
  391.                     FFormItems.Values[LabelStr] := ValueStr;
  392.                     LabelStr := '';
  393.                     ValueStr := '';
  394.                   end;
  395.  
  396.                   read1ln(S); // read another line
  397.  
  398.                   if Pos('Content-Type', S) <> 0 then
  399.                   begin
  400.                     LabelStr := UniqueLabelStr('CONTENT-TYPE');
  401.                     System.Delete(S, 1, Pos(':', S)+1); // delete to :
  402.                     ValueStr := System.Copy(S, 1, Length(S)); // copy name
  403.  
  404.                     if ValueStr <> '' then
  405.                     begin
  406.                       FFormItems.Values[LabelStr] := ValueStr;
  407.                       LabelStr := '';
  408.                       ValueStr := '';
  409.                     end;
  410.  
  411.                     read1ln(S); // read another line
  412.                   end;
  413.  
  414.                   if S = #13#10 then  // if there is content...
  415.                   begin
  416.                     AttachStream := TMemoryStream.Create;
  417.                     try
  418.                       // copy to memory stream
  419.                       readAttachment;
  420.                       // create new file name
  421.                       UniqueFileName := UpldrDir+ChangeFileExt(ExtractFileName(
  422.                         FFormItems.Values[LastLabelStr]), '')+FloatToStr(
  423.                           TimeStampToMSecs(DateTimeToTimeStamp(Time)))+IntToStr(FileCounter)+
  424.                             ExtractFileExt(FFormItems.Values[LastLabelStr]);
  425.                       // write file to disk
  426.                       AttachStream.SaveToFile(UniqueFileName);
  427.                       // save temp file name as form variable
  428.                       FFormItems.Values[UniqueLabelStr('TEMPFILE')] := UniqueFileName;
  429.                       Inc(FileCounter);
  430.                     finally
  431.                       AttachStream.Free;
  432.                     end;
  433.                   end;
  434.  
  435.                 end;
  436.                 Break;
  437.               end;
  438.               if Pos(CGIItems.Values['CONTENT BOUNDARY'], S) <> 0 then
  439.               begin
  440.                 // remove first 2 chars
  441.                 System.Delete(S, 1, 2);
  442.                 // check for Eom
  443.                 System.Delete(S, 1, Length(CGIItems.Values['CONTENT BOUNDARY']));
  444.                 if S = '--'#13#10 then Eom := true;
  445.                 Break;
  446.               end;
  447.               ValueStr := ValueStr + S; // append to valuestr
  448.               read1ln(S); // read another line
  449.             end;
  450.           end;
  451.           if ValueStr <> '' then
  452.           begin
  453.             // remove CRLFs from the end
  454.             FFormItems.Values[LabelStr] := System.Copy(ValueStr, 1, Length(ValueStr)-2);
  455.             LabelStr := '';
  456.             ValueStr := '';
  457.           end;
  458.         end;
  459.       finally
  460.         CloseFile(InputFCB);
  461.       end;
  462.     end;
  463.   end;
  464. end;
  465.  
  466. function TCGI.UniqueLabelStr(Value: string): string;
  467. var
  468.   Counter: integer;
  469. begin
  470.   Result := Value;
  471.   Counter := 0;
  472.   while FFormItems.IndexOfName(Result) <> -1 do
  473.   begin
  474.     Inc(Counter);
  475.     Result := Result+IntToStr(Counter);
  476.   end;
  477. end;
  478. // end of addition
  479.  
  480. procedure TCGI.LoadWinCGIUserData;
  481. { Copies values for WinCGI form variables. }
  482. begin
  483.   { All form variables are found in the [Form Literal] section of
  484.     the profile file. }
  485.   WinCGIProfile.ReadSectionValues('Form Literal', TStrings(FFormItems));
  486. end;
  487.  
  488. procedure TCGI.UnpackURLString( S: PChar );
  489. { Parses and decodes a URL-encoded string.  Copies variable values into
  490.   the FFormItems field. }
  491. var
  492.   LabelStr: String;
  493.   ValueStr: String;
  494.   Counter: integer;
  495. begin
  496.   Counter := 0;
  497.   LabelStr := '';
  498.   ValueStr := '';
  499.   while S^ <> #0 do
  500.   begin
  501.     case S^ of
  502.       '+' : ValueStr := ValueStr + ' ';
  503.       '%' : begin
  504.               ValueStr := ValueStr + Chr(StrToInt('$' + (S + 1)^ + (S + 2)^));
  505.               Inc(S, 2);
  506.             end;
  507.       '=' : if LabelStr = '' then
  508.             begin
  509.               LabelStr := ValueStr;
  510.               ValueStr := '';
  511.             end;
  512.       '&' : begin
  513.               while FFormItems.IndexOfName(LabelStr) <> -1 do
  514.               begin
  515.                 LabelStr := LabelStr+IntToStr(Counter);
  516.                 Inc(Counter);
  517.               end;
  518.               FFormItems.Values[LabelStr] := ValueStr;
  519.               ValueStr := '';
  520.               LabelStr := '';
  521.             end;
  522.       else ValueStr := ValueStr + S^;
  523.     end;
  524.     Inc(S);
  525.   end;
  526.  
  527.   if ValueStr <> '' then
  528.     FFormItems.Values[UniqueLabelStr(LabelStr)] := ValueStr;
  529. end;
  530.  
  531. { Commented out by Paul Warren}
  532.  
  533. (*procedure TCGI.Write(Value: String);
  534. { Standard Write to the output page. }
  535. begin
  536.   System.Write(OutputFile, Value);
  537. end;
  538.  
  539. procedure TCGI.WriteLn(Value: String);
  540. { Standard WriteLn to the output page. }
  541. begin
  542.   System.WriteLn(OutputFile, Value);
  543. end;*)
  544.  
  545. initialization
  546.   CGI := TCGI.Create;
  547. finalization
  548.   CGI.Free;
  549. end.
  550.